package com.mnsq.common.aop;

import com.mnsq.common.bean.annotation.AutoCache;
import com.mnsq.common.service.RedisServer;
import lombok.extern.slf4j.Slf4j;
import matrix.module.common.helper.encrypt.MD5;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * @author Administrator
 */
@Aspect
@Slf4j
@Component
@EnableAspectJAutoProxy
public class CacheAspect {

    @Autowired
    private RedisServer<String, Object> redisServer;

    private static List<String> exceptFields = new ArrayList<String>() {{
        add("imei");
    }};

    @Around(value = "execution(* *.*(..)) && @annotation(autoCache)")
    public Object around(ProceedingJoinPoint pjp, AutoCache autoCache) {
        try {
            Object[] args = pjp.getArgs();
            if (args != null && args.length > 0) {
                String key = getKey(args, autoCache);
                Object o = redisServer.get(key, autoCache.cls());
                if (o != null) {
                    if (autoCache.renew()) {
                        redisServer.renew(key, getTimes(autoCache));
                    }
                    return o;
                }
                Object proceed = pjp.proceed();
                redisServer.setValue(key, proceed, getTimes(autoCache));
                return proceed;
            }
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }

    private Long getTimes(AutoCache autoCache) {
        Long seconds = getTime(autoCache.cacheType(), autoCache.value());
        if (!autoCache.fixedValue()) {
            Random random = new Random();
            seconds *= random.nextInt(10);
        }
        if(seconds <= 0L){
            seconds = getTimes(autoCache);
        }
        return seconds;
    }

    private static String getKey(Object[] args, AutoCache autoCache) {
        String[] properties = autoCache.properties();
        List<String> excepts = Arrays.asList(autoCache.exceptFields());
        String prefix = autoCache.prefix();
        Object arg = args[0];
        StringBuilder key = new StringBuilder();
        if (arg instanceof String
                || arg instanceof Long
                || arg instanceof Integer
                || arg instanceof Short
                || arg instanceof Boolean
                || arg instanceof Double
                || arg instanceof Float
                || arg instanceof Byte
                ) {
            key.append(arg.toString());
        } else if (properties.length >= 1 && StringUtils.isNoneBlank(properties[0])) {
            for (String pName : properties) {
                try {
                    Field declaredField = arg.getClass().getDeclaredField(pName);
                    declaredField.setAccessible(true);
                    key.append(declaredField.get(arg));
                } catch (Exception e) {
                    try {
                        Field declaredField = arg.getClass().getSuperclass().getDeclaredField(pName);
                        declaredField.setAccessible(true);
                        key.append(declaredField.get(arg));
                    } catch (Exception e1) {
                        try {
                            Field declaredField = arg.getClass().getSuperclass().getSuperclass().getDeclaredField(pName);
                            declaredField.setAccessible(true);
                            key.append(declaredField.get(arg));
                        } catch (Exception e2) {
                            continue;
                        }
                    }
                }
            }
        } else {
            for (Field fd : arg.getClass().getDeclaredFields()) {
                try {
                    if (exceptFields.contains(fd.getName()) || excepts.contains(fd.getName())) {
                        continue;
                    }
                    fd.setAccessible(true);
                    key.append(fd.get(arg));
                } catch (Exception e) {
                    continue;
                }
            }
            try {
                for (Field fd : arg.getClass().getSuperclass().getDeclaredFields()) {
                    try {
                        if (exceptFields.contains(fd.getName()) || excepts.contains(fd.getName())) {
                            continue;
                        }
                        fd.setAccessible(true);
                        key.append(fd.get(arg));
                    } catch (Exception e) {
                        continue;
                    }
                }
            } catch (Exception e) {
            }
            try {
                for (Field fd : arg.getClass().getSuperclass().getSuperclass().getDeclaredFields()) {
                    try {
                        if (exceptFields.contains(fd.getName()) || excepts.contains(fd.getName())) {
                            continue;
                        }
                        fd.setAccessible(true);
                        key.append(fd.get(arg));
                    } catch (Exception e) {
                        continue;
                    }
                }
            } catch (Exception e) {
            }
        }
        log.info("key is " + prefix + ":" + key.toString() + "--" + "Md5 is " + MD5.get32(key.toString()));
        return prefix + ":" + MD5.get32(key.toString());
    }

    private static Long getTime(AutoCache.CacheType cacheType, Long time) {
        if (cacheType == null && time != null && time > 0L) {
            return time;
        }
        if (time == null || time <= 0L) {
            time = 1L;
        }
        Long resTime;
        if (AutoCache.CacheType.MINS.equals(cacheType)) {
            resTime = time * 60;
        } else if (AutoCache.CacheType.HOURS.equals(cacheType)) {
            resTime = time * 60 * 60;
        } else if (AutoCache.CacheType.DAYS.equals(cacheType)) {
            resTime = time * 60 * 60 * 24;
        } else if (AutoCache.CacheType.MONTHS.equals(cacheType)) {
            resTime = time * 60 * 60 * 24 * 30;
        } else if (AutoCache.CacheType.YEARS.equals(cacheType)) {
            resTime = time * 60 * 60 * 24 * 365;
        } else {
            resTime = time * 60 * 60 * 2;
        }
        return resTime;
    }
}
